查看原文
其他

Frida hook Java/Native与init_array 自吐最终方案

Simp1er 看雪学院 2021-05-14

本文为看雪论坛文章

看雪论坛作者ID:Simp1er



一、前言




题目来自看雪2020春季班10月份第一题和第三题。这两个题目本身难度不高,但是确实考察对源码的理解,同时也不失为一个frida的案例。


二、HTTPUrlConnection 信息自吐




1. 题目描述


在HttpURLConnection的开发过程中,设置参数时会用到以下方法。分析并hook上这些方法,写出一两个参数的自吐。
// URLConnectionhttpUrlConnection.setDoOutput(true);httpUrlConnection.setDoInput(true);HttpUrlConnection.setUseCaches(false);httpUrlConnection.setRequestProperty("Content-type", "application/x-java-serialized-object");httpUrlConnection.connect();// HttpURLConnectionhttpUrlConnection.setRequestMethod("POST");

2. 分析


具体在测试时可以自己写一个简单的demo并在APP中显示调用如下函数:


测试app功能正常后,用Objection直接把以下两个类全都hook上:
java.net.URLConnection 以及java.net.HttpURLConnection
 
结果如图发现反复调用这几个函数,其他函数都没有调用:
 

怀疑底层是否真实使用这些类实现的,找到对应源码,发现其实HttpURLConnection类也是一个抽象类,就是说在Java中实际上是其Impl类去实现:
 
使用WallBreaker去搜索HttpURLConnection会发现确实有一个类:
com.android.okhttp.internal.huc.HttpURLConnectionImpl


去源码里搜了搜发现,确实这个类实现了这个函数。
 
源码看HttpURLConnectionImpl:
https://android.googlesource.com/platform/external/okhttp/+/2946265382960fbd8e2bd765e40e3bc7016e960e/src/main/java/com/squareup/okhttp/internal/http/HttpURLConnectionImpl.java


晕死,结果这个类也是okHttp做的底层?先hook一下这个类。
 
会发现这个实现类确实实现了之前没有hook到的三个函数。


那么最终脚本就直接写就是了:
function hook_java(){ Java.perform(function(){ // httpUrlConnection.setDoOutput(true); // httpUrlConnection.setDoInput(true); // HttpUrlConnection.setUseCaches(false); var URLConnection = Java.use("java.net.URLConnection") URLConnection.setDoOutput.implementation = function(isOutput){ console.log("URLConnection.setDoOutput : ",isOutput); return this.setDoOutput(isOutput); } URLConnection.setDoInput.implementation = function(isInput){ console.log("URLConnection.setDoInput : ",isInput); return this.setDoInput(isInput); } URLConnection.setUseCaches.implementation = function(isUseCaches){ console.log("URLConnection.setUseCaches : ",isUseCaches); return this.setUseCaches(isUseCaches); } // com.android.okhttp.internal.huc.HttpURLConnectionImpl var HttpURLConnectionImpl = Java.use("com.android.okhttp.internal.huc.HttpURLConnectionImpl"); HttpURLConnectionImpl.setRequestProperty.implementation = function(name,value){ console.log("HttpURLConnectionImpl.setRequestProperty => ",name,": ",value); return this.setRequestProperty(name,value); } HttpURLConnectionImpl.setRequestMethod.implementation = function(type){ console.log("HttpURLConnectionImpl.setRequestMethod : ",type); return this.setRequestMethod(type); } HttpURLConnectionImpl.connect.implementation = function(){ console.log("HttpURLConnectionImpl.connect"); return this.connect(); } });} function main(){ hook_java();}setImmediate(main);

最终效果如下:



三、so构造函数自吐




1. 题目描述


在课时⑨中给出的init array的自吐 hook_linker,32位版本解决了,请分析64位版本,尝试给出解决方案并解决。

2. 分析


以8.1为例,查看源码后会发现。
 
dlopen调用过程中最终是:
 
目录/bionic/linker/linker_soinfo.cpp
soinfo::call_constructors() call_function("DT_INIT", init_func_, get_realpath()); call_array("DT_INIT_ARRAY", init_array_, init_array_count_, false, get_realpath());            ------>循环调用了 call_function("function", functions[i], realpath);


这个函数是模版函数,把linker拷到IDA上一看,会发现这个函数是唯一的,且参数是4个,同时会发现64位的linker中是没有call_function这个函数的,这个函数被优化成了代码片段,插进call_constructors函数和call_array函数中了。

最终模仿这个函数直接写一个js版本就行了关键的代码如下:
function hook_init_array() { //console.log("hook_constructor",Process.pointerSize); if (Process.pointerSize == 4) { var linker = Process.findModuleByName("linker"); }else if (Process.pointerSize == 8) { var linker = Process.findModuleByName("linker64"); } var addr_call_array = null; if (linker) { var symbols = linker.enumerateSymbols(); for (var i = 0; i < symbols.length; i++) { var name = symbols[i].name; if (name.indexOf("call_array") >= 0) { addr_call_array = symbols[i].address; } } } if (addr_call_array) { Interceptor.attach(addr_call_array, { onEnter: function (args) { this.type = ptr(args[0]).readCString(); //console.log(this.type,args[1],args[2],args[3]) if (this.type == "DT_INIT_ARRAY") { this.count = args[2]; //this.addrArray = new Array(this.count); this.path = ptr(args[3]).readCString(); var strs = new Array(); //定义一数组 strs = this.path.split("/"); //字符分割 this.filename = strs.pop(); if(this.count > 0){ console.log("path : ", this.path); console.log("filename : ", this.filename); } for (var i = 0; i < this.count; i++) { console.log("offset : init_array["+i+"] = ", ptr(args[1]).add(Process.pointerSize*i).readPointer().sub(Module.findBaseAddress(this.filename))); //插入hook init_array代码 } } }, onLeave: function (retval) { } }); }}

但是这样只是hook了so中init_array节中函数,还存在.init_proc的构造函数并未hook,和32位一样本来是继续去hook call_function函数,在脱出/system/lib64/libart.so后,发现call_function这个symbol无法找到,观察下图发现这个函数被inline了。



但是仔细观察.init_proc和.init_array函数调用前后,都会有一个log的判断,直接去hook这个_dl_async_safe_format_log函数吧。
 
但是首先得_dl_g_ld_debug_verbosity这个值大于等于2这个函数才会执行,那么先使用frida去这个变量的地址,然后修改这个变量的值使其达到_dl_async_safe_format_log函数会执行的条件即可。
 
最终frida关键代码如下:
function hook_constructor() { if (Process.pointerSize == 4) { var linker = Process.findModuleByName("linker"); } else { var linker = Process.findModuleByName("linker64"); } var addr_call_function =null; var addr_g_ld_debug_verbosity = null; var addr_async_safe_format_log = null; if (linker) { //console.log("found linker"); var symbols = linker.enumerateSymbols(); for (var i = 0; i < symbols.length; i++) { var name = symbols[i].name; if (name.indexOf("call_function") >= 0){ addr_call_function = symbols[i].address; // console.log("call_function",JSON.stringify(symbols[i])); } else if(name.indexOf("g_ld_debug_verbosity") >=0){ addr_g_ld_debug_verbosity = symbols[i].address; ptr(addr_g_ld_debug_verbosity).writeInt(2); } else if(name.indexOf("async_safe_format_log") >=0 && name.indexOf('va_list') < 0){ // console.log("async_safe_format_log",JSON.stringify(symbols[i])); addr_async_safe_format_log = symbols[i].address; } } } if(addr_async_safe_format_log){ Interceptor.attach(addr_async_safe_format_log,{ onEnter: function(args){ this.log_level = args[0]; this.tag = ptr(args[1]).readCString() this.fmt = ptr(args[2]).readCString() if(this.fmt.indexOf("c-tor") >= 0 && this.fmt.indexOf('Done') < 0){ this.function_type = ptr(args[3]).readCString(), // func_type this.so_path = ptr(args[5]).readCString(); var strs = new Array(); //定义一数组 strs = this.so_path.split("/"); //字符分割 this.so_name = strs.pop(); this.func_offset = ptr(args[4]).sub(Module.findBaseAddress(this.so_name)) console.log("func_type:", this.function_type, '\nso_name:',this.so_name, '\nso_path:',this.so_path, '\nfunc_offset:',this.func_offset ); // hook代码在这加 } }, onLeave: function(retval){ } }) } }

最终效果如下,
 
64位:


32位效果:


同样的完整代码已经上传github,看这里:
https://github.com/Simp1er/AndroidSec/blob/master/hook_constructors.js



四、后记




一切答案都在源码中。


- End -




看雪ID:Simp1er

https://bbs.pediy.com/user-home-715334.htm

  *本文由看雪论坛 Simp1er  原创,转载请注明来自看雪社区。



《安卓高级研修班》2021年6月班火热招生中!


# 往期推荐





公众号ID:ikanxue
官方微博:看雪安全
商务合作:wsc@kanxue.com



球分享

球点赞

球在看



点击“阅读原文”,了解更多!

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存